package tinybase.ix;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;

import junit.framework.Assert;
import tinybase.basic.BytePointer;
import tinybase.basic.RC;
import tinybase.basic.RedBase;
import tinybase.basic.RedBase.ClientHint;
import tinybase.basic.RedBase.CompOp;
import tinybase.pf.PF_FileHandle;
import tinybase.pf.PF_PageHandle;
import tinybase.pf.PageNum;
import tinybase.pf.Slot;
import tinybase.rm.RID;
import tinybase.rm.RM_Internal;

public class IndexScan {

	/*
	 * private attributes
	 */

	private boolean bScanOpen;
	private int curNodeNum;
	private int curEntry;
	private RID lastRid;

	private IndexHandle indexHandle;
	private CompOp compOp;
	private Object value;
	private ClientHint pinHint;

	/*
	 * public attributes and functions
	 */

	public IndexScan() {
		bScanOpen = false;
		curNodeNum = 0;
		curEntry = 0;

		indexHandle = null;
		compOp = CompOp.NO_OP;
		value = null;
		pinHint = ClientHint.NO_HINT;

	}

	// Open index scan

	/*
	 * discuss its parameter according IX_DEFAULT_COMPOP
	 */

	public RC openScan(IndexHandle indexHandle, Object value, CompOp compOp,
			ClientHint pinHint) {
		RC rc;
		RID zeroRid = new RID(0, 0);
		if (bScanOpen == true)
			return (RC.IX_SCANOPEN);
		if (indexHandle.getAttrLength() == 0)
			return (RC.IX_CLOSEDFILE);

		switch (compOp) {
		case EQ_OP:
		case LT_OP:
		case GT_OP:
		case LE_OP:
		case GE_OP:
			if (value == null)
				return (RC.IX_NULLPOINTER);
		case NO_OP:
			break;

		default:
			return (RC.IX_INVALIDCOMPOP);
		}

		this.indexHandle = indexHandle;
		this.compOp = compOp;
		this.value = value;
		this.pinHint = pinHint;

		this.bScanOpen = true;
		this.curNodeNum = 0;
		this.curEntry = 0;
		this.lastRid = zeroRid;

		// //C++ -> JAVA??
		if ((rc = findEntryAtNode(0)) != RC.SUCCESS) {
			return rc;
		}
		// //
		return RC.SUCCESS;

	}

	// Get the next matching entry return IX_EOF if no more matching
	// entries.
	public RC getNextEntry(RID rid) {
		RC rc;
		PF_PageHandle pageHandle = new PF_PageHandle();
		BytePointer pNode = new BytePointer();
		int nextNodeNum = PageNum.PF_PAGE_UNSET;

		// Sanity Check: 'this' must be open
		if (bScanOpen == false)
			// Test: closed IX_IndexScan
			return RC.IX_CLOSEDSCAN;

		// EOF
		if (curNodeNum == IX_Internal.IX_NO_MORE_NODE)
			return RC.IX_EOF;

		// Pin
		while ((rc = indexHandle.getPfFileHandle().getThisPage(
				curNodeNum, pageHandle)) != RC.SUCCESS) {
			// When the last leaf node become root node due to deletion,
			// curNodeNum must be invalid.
			Assert.assertTrue(curNodeNum != 0);
			curNodeNum = 0;
		}

		if ((rc = pageHandle.getData(pNode)) != RC.SUCCESS) {
			return rc;
		}
		if (curEntry == 0) {
			// System.out.println("compOp= " + compOp);
			switch (compOp) {
			case EQ_OP:
				// System.out.println("value= " + value.toString());
				// byte[] tmp= leafKey(pNode.getArray(), curEntry);
				// ByteBuffer buf= ByteBuffer.wrap(tmp);
				// System.out.println("LeafKey is: " + buf.getInt());
				if (compare(value, leafKey(pNode.getArray(), curEntry)) != 0) {
					// Unpin
					if ((rc = indexHandle.getPfFileHandle().unpinPage(
							curNodeNum)) != RC.SUCCESS) {
						return rc;
					}
					return RC.IX_EOF;
				}
				break;
			case LE_OP:
				if (compare(value, leafKey(pNode.getArray(), curEntry)) < 0) {
					// Unpin
					if ((rc = indexHandle.getPfFileHandle().unpinPage(
							curNodeNum)) != RC.SUCCESS) {
						return rc;
					}
					return RC.IX_EOF;
				}
				break;
			case LT_OP:
				if (compare(value, leafKey(pNode.getArray(), curEntry)) <= 0) {
					// Unpin
					if ((rc = indexHandle.getPfFileHandle().unpinPage(
							curNodeNum)) != RC.SUCCESS) {
						return rc;
					}
					return RC.IX_EOF;
				}
				break;
			default:
				break;
			}
		}

		// Copy rid
		rid.getFromBytes(leafRID(pNode.getArray(), curEntry));
		if (rid.equals(lastRid)) {
			curEntry++;
			rid.getFromBytes(leafRID(pNode.getArray(), curEntry));
		}
		lastRid.setRID(rid);

		// Advance the pointer
		IX_PageHdr ixPageHdr = new IX_PageHdr(pNode.getArray(),
				PF_PageHandle.DATASTART_OFFSET);
		if (curEntry == ixPageHdr.numKeys - 1) {
			if (curNodeNum == 0) {
				nextNodeNum = IX_Internal.IX_NO_MORE_NODE;
			} else
				nextNodeNum = ixPageHdr.nextNode;
			curEntry = 0;
		} else {
			nextNodeNum = curNodeNum;
			switch (compOp) {
			case EQ_OP:
				if (compare(value, leafKey(pNode.getArray(), curEntry + 1)) != 0)
					nextNodeNum = IX_Internal.IX_NO_MORE_NODE;
				break;
			case LE_OP:
				if (compare(value, leafKey(pNode.getArray(), curEntry + 1)) < 0)
					nextNodeNum = IX_Internal.IX_NO_MORE_NODE;
				break;
			case LT_OP:
				if (compare(value, leafKey(pNode.getArray(), curEntry + 1)) <= 0)
					nextNodeNum = IX_Internal.IX_NO_MORE_NODE;
				break;
			default:
				break;
			}
		}

		// Unpin
		if ((rc = indexHandle.getPfFileHandle().unpinPage(
				curNodeNum)) != RC.SUCCESS) {
			return rc;
		}

		curNodeNum = nextNodeNum;

		// Return ok
		return RC.SUCCESS;

	}

	// Close index scan
	public RC closeScan() {
		RID zeroRid = new RID(0, 0);

		// Sanity Check: 'this' must be open
		if (!bScanOpen)
			// Test: closed IX_IndexScan
			return RC.IX_CLOSEDSCAN;

		// Reset member variables
		bScanOpen = false;
		curNodeNum = 0;
		curEntry = 0;
		lastRid = zeroRid;

		indexHandle = null;
		compOp = RedBase.CompOp.NO_OP;
		value = null;
		pinHint = RedBase.ClientHint.NO_HINT;

		// Return ok
		return RC.SUCCESS;
	}

	/*
	 * private functions
	 */

	// Copy constructor
	private IndexScan(IndexScan fileScan) {

	}

	private int getInternalEntrySize() {
		return PageNum.PAGENUM_SIZE + indexHandle.getAttrLength();
	}

	private final byte[] internalKey(byte[] base, int idx) {
		byte[] arr = Arrays.copyOfRange(base, getInternalKeyOffset(idx),
				getInternalOffset(idx + 1));
		return arr;
	}

	private int getInternalOffset(int idx) {
		return PF_PageHandle.DATASTART_OFFSET + IX_Internal.IX_PAGEHDR_SIZE
				+ idx * this.getInternalEntrySize();
	}

	private int getInternalKeyOffset(int idx) {
		return getInternalOffset(idx) + PageNum.PAGENUM_SIZE;
	}

	private final byte[] internalPtr(byte[] base, int idx) {
		return base;
	}

	private final int leafEntrySize() {
		return indexHandle.getAttrLength() + RM_Internal.RM_RID_SIZE;
	}

	private final byte[] leafKey(byte[] base, int idx) {
		byte[] arr = Arrays.copyOfRange(base, getLeafKeyOffset(idx),
				getLeafRIDOffset(idx));
		return arr;
	}

	private final byte[] leafRID(byte[] base, int idx) {
		byte[] arr = Arrays.copyOfRange(base, getLeafRIDOffset(idx),
				getLeafKeyOffset(idx + 1));
		return arr;
	}

	private final int getLeafKeyOffset(int idx) {
		return PF_PageHandle.DATASTART_OFFSET + IX_Internal.IX_PAGEHDR_SIZE
				+ idx * leafEntrySize();
	}

	private final int getLeafRIDOffset(int idx) {
		return getLeafKeyOffset(idx) + indexHandle.getAttrLength();
	}

	private int compare(Object object, byte[] value) {
		return compare(object, value, 0);
	}

	private int compare(Object pData, byte[] pNode, int leafKeyOffset) {
		if (pData instanceof byte[]) {
			return compare2((byte[]) pData, pNode, leafKeyOffset);
		} else {
			return compare1(pData, pNode, leafKeyOffset);
		}
	}

	private int compare1(Object object, byte[] value, int offset) {
		int cmp = 0;
		int i;
		float f;
		switch (indexHandle.getAttrType()) {
		case INT:
			i = ByteBuffer.wrap(value).getInt(offset);
			cmp = new Integer(i).compareTo((Integer) object);
			break;
		case FLOAT:
			f = ByteBuffer.wrap(value).getFloat(offset);
			cmp = new Float(f).compareTo((Float) object);
			break;
		case STRING:
			byte[] strBuf = new byte[indexHandle.getAttrLength()];
			System.arraycopy(value, offset, strBuf, 0,
					indexHandle.getAttrLength());
			String dataString = new String(strBuf, Charset.forName("ASCII"));
			cmp = dataString.compareTo((String) object);
			break;
		}
		return 0 - cmp;
	}

	private int compare2(byte[] _value, byte[] value, int offset) {
		int cmp = 0;
		int i, i0;
		float f, f0;
		switch (indexHandle.getAttrType()) {
		case INT:
			i = ByteBuffer.wrap(value).getInt(offset);
			i0 = ByteBuffer.wrap(_value).getInt(0);
			cmp = i - i0;
			break;
		case FLOAT:
			f = ByteBuffer.wrap(value).getFloat(offset);
			f0 = ByteBuffer.wrap(_value).getFloat(0);
			cmp = Float.compare(f, f0);
			break;
		case STRING:
			byte[] strBuf = new byte[indexHandle.getAttrLength()];
			System.arraycopy(value, offset, strBuf, 0,
					indexHandle.getAttrLength());
			String dataString = new String(strBuf, Charset.forName("ASCII"));
			cmp = dataString.compareTo(new String(_value, Charset
					.forName("ASCII")));
			break;
		}
		return 0 - cmp;
	}

	//
	// FindEntryAtNode
	//
	// Desc:
	// In: nodeNum -
	// Ret: PF reeturn code
	//
	private RC findEntryAtNode(int nodeNum) {

		RC rc = RC.SUCCESS;
		PF_PageHandle pageHandle = new PF_PageHandle();
		BytePointer pNode = new BytePointer();
		int numKeys = 0;

		// Pin
		if ((rc = indexHandle.getPfFileHandle().getThisPage(
				nodeNum, pageHandle)) != RC.SUCCESS) {
			return rc;
		}
		if ((rc = pageHandle.getData(pNode)) != RC.SUCCESS) {
			return rc;
		}

		IX_PageHdr ixPageHdr = new IX_PageHdr(pNode.getArray(),
				PF_PageHandle.DATASTART_OFFSET);

		numKeys = ixPageHdr.numKeys;
		// Current node is LEAF node
		if ((ixPageHdr.flags & IX_Internal.IX_LEAF_NODE) == 1) {
			int cmp = 0;

			if (numKeys == 0) {
				curNodeNum = IX_Internal.IX_NO_MORE_NODE;
				if ((rc = indexHandle.getPfFileHandle().unpinPage(
						nodeNum)) != RC.SUCCESS) {
					return rc;
				}
				return RC.SUCCESS;
			}

			switch (compOp) {
			case NO_OP:
				curEntry = 0;
				curNodeNum = nodeNum;
				break;

			case LE_OP:
				curEntry = 0;
				if (compare(value, leafKey(pNode.getArray(), 0)) >= 0)
					curNodeNum = nodeNum;
				else
					curNodeNum = IX_Internal.IX_NO_MORE_NODE;

				break;

			case LT_OP:
				curEntry = 0;
				if (compare(value, leafKey(pNode.getArray(), 0)) > 0)
					curNodeNum = nodeNum;
				else
					curNodeNum = IX_Internal.IX_NO_MORE_NODE;
				break;

			case EQ_OP:
				// System.out.println("Run here : IndexScan 430");
				for (curEntry = 0; curEntry < numKeys; curEntry++) {
					cmp = compare(value, leafKey(pNode.getArray(), curEntry));
					if (cmp <= 0)
						break;
				}
				if ((curEntry == numKeys || cmp < 0)) {
					curNodeNum = IX_Internal.IX_NO_MORE_NODE;
				} else {
					if (curEntry == 0
							&& nodeNum != 0
							&& ixPageHdr.prevNode != IX_Internal.IX_NO_MORE_NODE) {

						int prevNode = ixPageHdr.prevNode;
						if ((rc = indexHandle.getPfFileHandle().unpinPage(
								nodeNum)) != RC.SUCCESS) {
							return rc;
						}
						// Recursively find the first occurrence at the previous
						// node}
						if ((rc = findEntryAtNode(prevNode)) != RC.SUCCESS) {
							return rc;
						}
						// Key doesn't exist at the previous node
						if (curNodeNum == IX_Internal.IX_NO_MORE_NODE) {
							curEntry = 0;
							curNodeNum = nodeNum;
						}
						return RC.SUCCESS;
					} else {
						curNodeNum = nodeNum;
					}
				}
				break;

			case GE_OP:
				for (curEntry = 0; curEntry < numKeys; curEntry++)
					if (compare(value, leafKey(pNode.getArray(), curEntry)) <= 0)
						break;
				if (curEntry == numKeys) {

					if (nodeNum == 0)
						curNodeNum = -1;
					curNodeNum = ixPageHdr.nextNode;
					curEntry = 0;
				} else {
					if (curEntry == 0
							&& nodeNum != 0
							&& ixPageHdr.prevNode != IX_Internal.IX_NO_MORE_NODE) {
						int prevNode = ixPageHdr.prevNode;
						// Unpin
						if ((rc = indexHandle.getPfFileHandle().unpinPage(
								nodeNum)) != RC.SUCCESS) {
							return rc;
						}
						return findEntryAtNode(prevNode);
					} else {
						curNodeNum = nodeNum;
					}
				}
				break;

			case GT_OP:

				for (curEntry = 0; curEntry < numKeys; curEntry++)
					if (compare(value, leafKey(pNode.getArray(), curEntry)) < 0)
						break;

				if (curEntry == 0 && nodeNum != 0)
					Assert.assertTrue(ixPageHdr.prevNode == IX_Internal.IX_NO_MORE_NODE);

				if (curEntry == numKeys) {
					if (nodeNum == 0)
						curNodeNum = IX_Internal.IX_NO_MORE_NODE;
					curNodeNum = ixPageHdr.nextNode;
					curEntry = 0;
				} else {
					curNodeNum = nodeNum;
				}
				break;
			}

			// Unpin
			if ((rc = indexHandle.getPfFileHandle().unpinPage(
					nodeNum)) != RC.SUCCESS) {
				return rc;

			}
		}
		// Current node is INTERNAL node
		else {
			int childNodeNum = PageNum.PF_PAGE_UNSET;
			int j;

			// Find the appropriate child to traverse
			switch (compOp) {
			case NO_OP:
			case LE_OP:
			case LT_OP:
				childNodeNum = ByteBuffer.wrap(pNode.getArray())
						.getInt(getInternalOffset(0));
				// memcpy(&childNodeNum, internalPtr(pNode, 0),
				// sizeof(PageNum));
				break;
			case EQ_OP:
			case GE_OP:
			case GT_OP:
				for (j = 0; j < numKeys; j++)
					if (compare(value, internalKey(pNode.getArray(), j)) < 0)
						break;
				// System.arraycopy(pNode, getInternalOffset(j), dest, destPos,
				// length)
				childNodeNum = ByteBuffer.wrap(pNode.getArray())
						.getInt(getInternalOffset(j));
				// memcpy(&childNodeNum, InternalPtr(pNode, j),
				// sizeof(PageNum));
				break;
			}

			// Unpin
			if ((rc = indexHandle.getPfFileHandle().unpinPage(
					nodeNum)) != RC.SUCCESS) {
				return rc;
			}
			// Recursively call FindEntryAtNode()
			if ((rc = findEntryAtNode(childNodeNum)) != RC.SUCCESS) {
				return rc;
			}
		}

		// Return ok
		return RC.SUCCESS;

		// Return error

	}
}
